/* Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "responder.h"
#include "transfer/mctp_transfer.h"
#include "context_config.h"

Responder::Responder(const string& deviceName, const string& componentType, const string& inventoryPath)
    : deviceName(deviceName), inventoryPath(inventoryPath), spdm_context(nullptr), msgTransfer(nullptr),
    trustedComponent(new TrustedComponent(deviceName, componentType, inventoryPath)), componentIntegrity(nullptr) {}

Responder::~Responder()
{
    if (spdm_context != nullptr) {
        free(spdm_context);
        spdm_context = nullptr;
    }

    if (msgTransfer != nullptr) {
        delete msgTransfer;
    }

    delete trustedComponent;
}

string Responder::getDeviceName() const
{
    return deviceName;
}

string Responder::getInventoryPath() const
{
    return inventoryPath;
}

void *Responder::NewSpdmCtx()
{
    if (spdm_context != nullptr) {
        debug_log(DLOG_ERROR, "has context, return");
        return spdm_context;
    }
    
    size_t context_size = GetContextSize();
    debug_log(DLOG_INFO, "Non secured context size = %u", context_size);

    spdm_context = static_cast<void *>(malloc(context_size));
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "malloc spdm context failed, size(%u)", context_size);
        return nullptr;
    }

    uint32_t res = InitContext(spdm_context);
    if (res != LIBSPDM_STATUS_SUCCESS) {
        debug_log(DLOG_ERROR, "init spdm context failed, res = %u", res);
        return nullptr;
    }

    return spdm_context;
}

int32_t Responder::InitTransfer(uint8_t type)
{
    if (msgTransfer != nullptr) {
        debug_log(DLOG_ERROR, "msg transfer existed, do not init again");
        return RET_ERR;
    }

    if (type == TRANSPORT_TYPE_MCTP) {
        msgTransfer = new MctpTransfer(this->deviceName);
    } else {
        debug_log(DLOG_ERROR, "invalid transport type(%u)", type);
        return RET_ERR;
    }

    return msgTransfer->RegisterFunctions(spdm_context);
}

void Responder::ConfigCtx()
{
    ZeroMem(&parameter, sizeof(parameter));
    parameter.location = LIBSPDM_DATA_LOCATION_LOCAL;

    uint8_t ct_exponent = 0;
    SpdmSetData(spdm_context, LIBSPDM_DATA_CAPABILITY_CT_EXPONENT, &parameter, &ct_exponent, sizeof(uint8_t));

    uint8_t measurement_spec = MEASUREMENT_SPEC;
    SpdmSetData(spdm_context, LIBSPDM_DATA_MEASUREMENT_SPEC, &parameter, &measurement_spec, sizeof(uint8_t));

    uint32_t asym_algo = ASYM_ALGO;
    SpdmSetData(spdm_context, LIBSPDM_DATA_BASE_ASYM_ALGO, &parameter, &asym_algo, sizeof(uint32_t));

    uint32_t hash_algo = HASH_ALGO;
    SpdmSetData(spdm_context, LIBSPDM_DATA_BASE_HASH_ALGO, &parameter, &hash_algo, sizeof(uint32_t));
}

int32_t Responder::InitSpdmCtx(uint8_t type)
{
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "no context, cannot init");
        return RET_ERR;
    }

    int32_t ret = this->InitTransfer(type);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "init spdm transfer failed");
        return RET_ERR;
    }
    this->ConfigCtx();
    
    return RET_OK;
}

bool Responder::CheckSpdmCtx()
{
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "no context, cannot check");
        return false;
    }

    return CheckContext(spdm_context);
}

void Responder::ReleaseSpdmCtx()
{
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "no context, cannot free");
        return;
    }

    free(spdm_context);
    spdm_context = nullptr;
}

static string GetSpdmVersion(void *spdm_context)
{
    libspdm_data_parameter_t parameter = {};
    parameter.location = LIBSPDM_DATA_LOCATION_CONNECTION;

    uint16_t version;
    size_t version_size = sizeof(version);

    SpdmGetData(spdm_context, LIBSPDM_DATA_SPDM_VERSION, &parameter, &version, &version_size);

    return GetCapabilityString("Version", version);
}

int32_t Responder::InitConnection()
{
    uint32_t ret = InitSpdmConnection(spdm_context, false);
    if (ret != LIBSPDM_STATUS_SUCCESS) {
        debug_log(DLOG_ERROR, "init spdm connection failed");
        return RET_ERR;
    }
    
    // 协商完成后才能拿到版本号
    string version = GetSpdmVersion(spdm_context);

    // 获取当前时间
    uint64_t now = time(nullptr);

    componentIntegrity = new ComponentIntegrity("SPDM", version, now);

    return RET_OK;
}

uint8_t Responder::GetDigests()
{
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "no context, cannot get digest");
        return 0xff;
    }

    uint8_t slot_diff_mask = 0;
    int32_t ret = trustedComponent->collection->GetDigests(spdm_context, &slot_diff_mask);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "try get %s digests failed", deviceName.c_str());
        // 如果获取失败，直接返回0xff，由上游尝试获取全量证书
        return 0xff;
    }

    return slot_diff_mask;
}

int32_t Responder::GetCertificateFromSpdm(uint8_t slot_id)
{
    if (spdm_context == nullptr) {
        debug_log(DLOG_ERROR, "no context, cannot get certificate");
        return RET_ERR;
    }

    return trustedComponent->collection->GetCertificate(spdm_context, slot_id);
}

Certificate *Responder::GetCertificate(uint8_t slot_id)
{
    if (slot_id >= MAX_SLOT_NUM) {
        debug_log(DLOG_ERROR, "invalid slot_id %u", slot_id);
        return nullptr;
    }

    CertificateCollection *collection = trustedComponent->collection;
    Certificate& cert = collection->collection[slot_id];
    return &cert;
}

int32_t Responder::ChallengeAuth(uint8_t slot_id)
{
    // 槽位数值过大或无效的槽位号
    if (slot_id >= MAX_SLOT_NUM || trustedComponent->collection->collection[slot_id].certCount == 0) {
        debug_log(DLOG_ERROR, "invalid slot_id %u", slot_id);
        return RET_ERR;
    }

    uint8_t measurement_hash[LIBSPDM_MAX_HASH_SIZE];

    ::ZeroMem(measurement_hash, sizeof(measurement_hash));
    // challenge verify的动作都在libspdm中，此处仅需要校验返回值即可
    uint32_t res = ::ChallengeAuth(spdm_context, nullptr, slot_id, SPDM_REQUEST_NO_MEASUREMENT_SUMMARY_HASH,
        measurement_hash, nullptr);
    if (res != LIBSPDM_STATUS_SUCCESS) {
        debug_log(DLOG_ERROR, "challenge for slot(%u) failed, ret = %u", slot_id, res);
        return RET_ERR;
    }

    return RET_OK;
}

int32_t Responder::InitMeasurements(uint8_t slot_id)
{
    // 槽位数值过大或无效的槽位号
    if (slot_id >= MAX_SLOT_NUM || trustedComponent->collection->collection[slot_id].certCount == 0) {
        debug_log(DLOG_ERROR, "invalid slot_id %u", slot_id);
        return RET_ERR;
    }

    if (componentIntegrity == nullptr) {
        debug_log(DLOG_ERROR, "cannot operate, no component integrity");
        return RET_ERR;
    }

    int32_t res = componentIntegrity->InitBaseMeasurements(spdm_context, slot_id);
    if (res != RET_OK) {
        debug_log(DLOG_ERROR, "init base measurements failed");
    }

    return res;
}


tuple<string, string, string, string, string> Responder::GetSignedMeasurement(vector<uint8_t> indices, uint8_t slot_id,
    const string& nonce)
{
    if (spdm_context == nullptr || componentIntegrity == nullptr || componentIntegrity->measurementSet == nullptr) {
        debug_log(DLOG_ERROR, "SPDM context not initialized");
        throw runtime_error("SPDM context not initialized");
    }

    // 槽位数值过大或无效的槽位号
    if (slot_id >= MAX_SLOT_NUM || trustedComponent->collection->collection[slot_id].certCount == 0) {
        debug_log(DLOG_ERROR, "invalid slot_id %u", slot_id);
        throw runtime_error("invalid slot_id");
    }

    MeasurementSet *map = componentIntegrity->measurementSet;

    return map->GetSignedMeasurement(spdm_context, indices, slot_id, nonce);
}